Анализ данных о сфере туризма Королевства Таиланд¶

В рамках данного проекта проводится разведочный анализ данных туристического сектора Таиланда за 2011-2020 годы (источник - Министрество Туризма и Спорта Королевства Таиланд).

По каждой стране прибытия известна информация о:
     1. Среднем времени пребывания (в днях)
     2. Количестве приезжающих первично/повторно (кол-во прибытий (пересечений границы))
     3. Количестве приезжающих по суше/воде/воздуху (кол-во прибытий (пересечений границы))
     4. Средней сумме ежедневных расходов по категориям (в тайских батах, THB)
     5.* Для лучшего представления о странах прибытия также было решено добавить данные о ВВП (в долларах США, USD) этих стран на заданном временном промежутке (источник - Всемирный Банк).

Изначально данные предоставляются в отдельных таблицах Excel(.xlsx). Для каждой таблицы необходимо провести предобработку данных и для удобства последующего анализа объединить все данные в один датасет.

Дополнением к разведочному анализу в данном блокноте является интерактивный дашборд, созданный в Tableau.

Загрузка и предварительная обработка данных¶

Загрузка необходимых библиотек

In [1]:
import warnings
warnings.filterwarnings('ignore')

import pandas as pd
import numpy as np
import plotly.express as px
import scipy.stats

1. Среднее время пребывания (в днях)¶

In [2]:
df = pd.read_excel(io=r"country_avg_length_stay.xlsx", engine='openpyxl')
df.Country = df.Country.apply(lambda x: x.replace("\xa0", ' ')) # Сразу решим проблему кодировки
df.sample(10)
Out[2]:
Continent Country 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
37 South Asia India 6.90 7.15 7.14 7.22 7.64 7.56 7.45 7.39 7.43 9.32
15 Europe Austria 14.75 16.06 16.16 15.99 16.19 16.04 16.51 16.59 16.55 18.29
26 Europe Sweden 18.55 19.75 19.51 19.48 19.38 18.89 19.14 19.13 19.15 20.06
32 The Americas Brazil 11.51 12.46 13.18 13.03 13.96 14.64 14.82 13.81 12.49 12.98
20 Europe Germany 17.09 18.03 17.83 17.53 17.00 17.36 17.47 17.29 17.37 18.43
23 Europe Norway 14.92 16.05 16.46 16.01 15.63 15.65 16.11 16.38 17.40 20.28
34 The Americas U.S.A. 14.01 14.87 14.56 14.33 13.38 13.98 14.18 14.33 14.19 14.71
28 Europe United Kingdom 17.35 18.13 17.80 17.13 17.29 17.90 18.25 17.71 17.83 18.79
45 Middle East Egypt 8.40 9.15 9.78 9.83 10.63 10.75 11.14 11.28 11.31 20.23
12 East Asia South Korea 7.30 7.64 7.66 7.44 7.72 7.72 7.60 7.22 7.20 10.12

Пропусков в данных нет

In [3]:
df.isna().sum()
Out[3]:
Continent    0
Country      0
2011         0
2012         0
2013         0
2014         0
2015         0
2016         0
2017         0
2018         0
2019         0
2020         0
dtype: int64

В таблице содержится информация по следующим странам

In [4]:
df.Country.sort_values().unique()
Out[4]:
array(['Argentina', 'Australia', 'Austria', 'Bangladesh', 'Belgium',
       'Brazil', 'Brunai', 'Cambodia', 'Canada', 'China', 'Denmark',
       'East Europe', 'Egypt', 'Finland', 'France', 'Germany',
       'Hong Kong', 'India', 'Indonesia', 'Israel', 'Italy', 'Japan',
       'Kuwait', 'Laos', 'Malaysia', 'Myanmar', 'Nepal', 'Netherlands',
       'New Zealand', 'Norway', 'Others', 'Pakistan', 'Philippines',
       'Republic of South Africa', 'Russia', 'Saudi Arabia', 'Singapore',
       'South Korea', 'Spain', 'Sri Lanka', 'Sweden', 'Switzerland',
       'Taiwan', 'U.S.A.', 'United Arab Emirates', 'United Kingdom',
       'Vietnam'], dtype=object)

Следует исключить из столбца "Country" Восточную Европу и категорию "Другие", т.к. нельзя сравнивать их с отдельными странами. Также переведем таблицу из широкого формата в длинный (для последующего соединения таблиц).

In [5]:
df = df.query("Country not in ('East Europe','Others')")
stay_len = pd.melt(df, id_vars='Country', value_vars=range(2011, 2021), var_name='Year', value_name='Average length of stay (days)').sort_values(['Country', 'Year'])
stay_len
Out[5]:
Country Year Average length of stay (days)
28 Argentina 2011 11.91
73 Argentina 2012 12.51
118 Argentina 2013 13.07
163 Argentina 2014 13.30
208 Argentina 2015 14.61
... ... ... ...
233 Vietnam 2016 5.98
278 Vietnam 2017 5.78
323 Vietnam 2018 6.54
368 Vietnam 2019 7.59
413 Vietnam 2020 11.53

450 rows × 3 columns

Посмотрим на boxplot по столбцу "Average length of stay (days)":

  • выбросов не обнаружено
  • данные находятся в пределах, не противоречащих здравому смыслу
In [6]:
fig = px.box(stay_len, y="Average length of stay (days)", color_discrete_sequence=['purple'])
fig.update_layout(title="Среднее время пребывания туриста (в днях)", title_x=0.5)
fig.show()

2. Количество приезжающих первично/повторно¶

In [7]:
df = pd.read_excel(io=r"country_visit_freq.xlsx", engine='openpyxl')
df.Country = df.Country.apply(lambda x: x.replace("\xa0", ' '))
df.sample(10)
Out[7]:
Continent Country Frequency of Visit 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
135 Oceania Others Total 6633 6599 6634 5919 5562 6119 2739 7917 8616 1458
27 East Asia China Total 1704800 2761213 4609717 4631981 7981407 8821148 9846818 10625167 11138658 1302137
70 Europe Netherlands First visit 59464 65514 76947 72267 78559 78653 70718 70995 75762 13876
102 The Americas Canada Total 170981 192602 209059 195429 210097 222358 74778 252763 252574 55984
97 The Americas Argentina First visit 7252 10800 13208 13199 16443 21812 33755 17678 10871 4604
98 The Americas Argentina Re-visit 4753 6095 6874 7209 11607 24578 62738 25807 17312 7857
99 The Americas Brazil Total 21231 26997 34176 43694 44414 63139 31995 57105 58970 18200
82 Europe Sweden First visit 80375 71109 74056 69681 79238 92917 67760 41593 50134 19717
156 Africa Republic of South Africa Total 66705 73530 73657 70574 74397 76541 41329 97071 89750 9692
53 Europe Denmark Re-visit 116417 118205 113319 112656 108672 112452 116655 136231 120314 48173

Пропусков в данных нет

In [8]:
df.isna().sum()
Out[8]:
Continent             0
Country               0
Frequency of Visit    0
2011                  0
2012                  0
2013                  0
2014                  0
2015                  0
2016                  0
2017                  0
2018                  0
2019                  0
2020                  0
dtype: int64

В таблице содержится информация по следующим странам

In [9]:
df.Country.sort_values().unique()
Out[9]:
array(['Argentina', 'Australia', 'Austria', 'Bangladesh', 'Belgium',
       'Brazil', 'Brunai', 'Cambodia', 'Canada', 'China', 'Denmark',
       'East Europe', 'Egypt', 'Finland', 'France', 'Germany',
       'Hong Kong', 'India', 'Indonesia', 'Ireland', 'Israel', 'Italy',
       'Japan', 'Kuwait', 'Laos', 'Malaysia', 'Myanmar', 'Nepal',
       'Netherlands', 'New Zealand', 'Norway', 'Others', 'Pakistan',
       'Philippines', 'Republic of South Africa', 'Russia',
       'Saudi Arabia', 'Singapore', 'South Korea', 'Spain', 'Sri Lanka',
       'Sweden', 'Switzerland', 'Taiwan', 'U.S.A.',
       'United Arab Emirates', 'United Kingdom', 'Vietnam'], dtype=object)

Нужно исключить Ирландию из столбца "Country", так как этой страны нет в других таблицах.

In [10]:
df = df.query("Country not in ('Ireland','East Europe','Others')")
visit_freq = pd.melt(df, id_vars=['Country', 'Frequency of Visit'], value_vars=range(2011, 2021), var_name='Year', value_name='Arrivals').sort_values(['Country', 'Year'])
visit_freq
Out[10]:
Country Frequency of Visit Year Arrivals
84 Argentina Total 2011 12005
85 Argentina First visit 2011 7252
86 Argentina Re-visit 2011 4753
219 Argentina Total 2012 16895
220 Argentina First visit 2012 10800
... ... ... ... ...
1105 Vietnam First visit 2019 361973
1106 Vietnam Re-visit 2019 718636
1239 Vietnam Total 2020 131308
1240 Vietnam First visit 2020 35654
1241 Vietnam Re-visit 2020 95654

1350 rows × 4 columns

Посмотрим на boxplot по столбцу "Arrivals", сгрупированному по столбцу "Frequency of Visit":

  • большой разброс данных обьясняется различием в объеме туристических потоков в Таиланд из разных стран
  • данные по обеим категориям находятся в схожих пределах, не противоречащих здравому смыслу (нет отрицательных значений или значений, отличающихся на порядок)
In [11]:
fig = px.box(visit_freq[visit_freq["Frequency of Visit"] != 'Total'], x="Frequency of Visit", y="Arrivals", color="Frequency of Visit", color_discrete_sequence=['purple', 'green', 'yellow'])
fig.update_layout(title="Количество прибытий туристов в год: первичные/повторные", title_x=0.5, showlegend=False)
fig.show()

В столбце "Frequency of Visit" значение "Total" (общее кол-во) является избыточной информацией, его необходимо удалить.

In [12]:
visit_freq = visit_freq.pivot(columns="Frequency of Visit", index=['Country', 'Year'], values='Arrivals').reset_index()
visit_freq.drop("Total", axis=1, inplace=True)
visit_freq
Out[12]:
Frequency of Visit Country Year First visit Re-visit
0 Argentina 2011 7252 4753
1 Argentina 2012 10800 6095
2 Argentina 2013 13208 6874
3 Argentina 2014 13199 7209
4 Argentina 2015 16443 11607
... ... ... ... ...
445 Vietnam 2016 341267 522540
446 Vietnam 2017 369763 615932
447 Vietnam 2018 395049 658674
448 Vietnam 2019 361973 718636
449 Vietnam 2020 35654 95654

450 rows × 4 columns

In [13]:
"""
countries = []
for country in visit_freq.Country.unique():
    countries.extend([country]*10)
    
years = []
for _ in range(len(visit_freq.Country.unique())):
    years.extend(visit_freq.Year.unique())

total_visit = visit_freq[visit_freq['Frequency of Visit'] == 'Total'].Arrivals.to_list()
first_visit = visit_freq[visit_freq['Frequency of Visit'] == 'First visit'].Arrivals.to_list()
re_visit = visit_freq[visit_freq['Frequency of Visit'] == 'Re-visit'].Arrivals.to_list()

calc_share = lambda x: first_visit[x] / total_visit[x] if total_visit[x] > re_visit[x] else first_visit[x] / re_visit[x]
first_share = [calc_share(i) if total_visit[i] != 0 and re_visit[i] != 0 else 0 for i in range(len(total_visit))]

first_time_visit = pd.DataFrame({'Country': countries, 'Year': years, 'Share of first time visits': first_visit})
first_time_visit.Country.unique()
"""
Out[13]:
"\ncountries = []\nfor country in visit_freq.Country.unique():\n    countries.extend([country]*10)\n    \nyears = []\nfor _ in range(len(visit_freq.Country.unique())):\n    years.extend(visit_freq.Year.unique())\n\ntotal_visit = visit_freq[visit_freq['Frequency of Visit'] == 'Total'].Arrivals.to_list()\nfirst_visit = visit_freq[visit_freq['Frequency of Visit'] == 'First visit'].Arrivals.to_list()\nre_visit = visit_freq[visit_freq['Frequency of Visit'] == 'Re-visit'].Arrivals.to_list()\n\ncalc_share = lambda x: first_visit[x] / total_visit[x] if total_visit[x] > re_visit[x] else first_visit[x] / re_visit[x]\nfirst_share = [calc_share(i) if total_visit[i] != 0 and re_visit[i] != 0 else 0 for i in range(len(total_visit))]\n\nfirst_time_visit = pd.DataFrame({'Country': countries, 'Year': years, 'Share of first time visits': first_visit})\nfirst_time_visit.Country.unique()\n"

3. Количестве приезжающих по суше/воде/воздуху¶

In [14]:
df = pd.read_excel(io=r"arrival_transport.xlsx", engine='openpyxl')
df.Country = df.Country.apply(lambda x: x.replace("\xa0", ' '))
df.sample(10)
Out[14]:
Continent Country Mode of Transport 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
29 East Asia Singapore Air 594644 753034 856167 794482 1029014 928077 998565 1037945 1025492 122772
198 Middle East Saudi Arabia Land 74 114 195 155 217 256 202 141 254 6
129 The Americas Argentina Air 10829 15096 18358 19340 25952 42861 60399 42196 27930 11658
145 The Americas Others Air 33520 43074 49079 53460 55421 70172 82472 87856 89939 17036
68 Europe Denmark Total 164096 167499 163186 160977 159420 165581 161920 169365 162448 66848
177 Oceania New Zealand Air 93517 105657 109814 102076 97208 106150 113649 112911 109397 14985
147 The Americas Others Waterway 2093 1283 2401 646 680 677 754 667 656 362
174 Oceania Australia Land 28524 30625 29648 24555 21351 20025 15027 13541 12756 1578
186 Middle East Egypt Land 86 70 164 66 107 97 120 112 106 12
131 The Americas Argentina Waterway 765 865 665 400 460 339 383 389 452 270

Пропусков в данных нет

In [15]:
df.isna().sum()
Out[15]:
Continent            0
Country              0
Mode of Transport    0
2011                 0
2012                 0
2013                 0
2014                 0
2015                 0
2016                 0
2017                 0
2018                 0
2019                 0
2020                 0
dtype: int64

Посмотрим на boxplot по столбцу "Arrivals", сгрупированному по столбцу "Frequency of Visit":

  • большой разброс данных обьясняется различием в объеме туристических потоков в Таиланд из разных стран
  • нет отрицательных значений или значений, отличающихся на порядок
  • прибывающих по воздуху значительно больше, что имеет логический смысл
In [16]:
df = df.query("Country not in ('Ireland','East Europe','Others')")
transport_type = pd.melt(df, id_vars=['Country', 'Mode of Transport'], value_vars=range(2011, 2021), var_name='Year', value_name='Arrivals').sort_values(['Country', 'Year'])

fig = px.box(transport_type[transport_type["Mode of Transport"] != 'Total'], x="Mode of Transport", y="Arrivals", color="Mode of Transport", color_discrete_sequence=['purple', 'green', 'yellow'])
fig.update_layout(title="Количество прибытий туристов в год: по воздуху/по суше/по воде", title_x=0.5, showlegend=False)
fig.show()

Из столбца "Mode of Transport" следует исключить избыточную категорию "Total".

In [17]:
transport_type = transport_type.pivot(columns='Mode of Transport', index=['Country', 'Year'], values='Arrivals').reset_index()
transport_type.drop('Total', axis=1, inplace=True)
transport_type
Out[17]:
Mode of Transport Country Year Air Land Waterway
0 Argentina 2011 10829 1376 765
1 Argentina 2012 15096 1892 865
2 Argentina 2013 18358 2012 665
3 Argentina 2014 19340 1698 400
4 Argentina 2015 25952 1638 460
... ... ... ... ... ...
445 Vietnam 2016 562358 219252 48610
446 Vietnam 2017 687299 201228 46652
447 Vietnam 2018 785034 197572 44654
448 Vietnam 2019 821096 186800 39767
449 Vietnam 2020 103654 6638 21835

450 rows × 5 columns

4. Средняя сумма ежедневных расходов по категориям¶

In [18]:
df = pd.read_excel(io=r"country_expenditure_type.xlsx", engine='openpyxl')
df.Country = df.Country.apply(lambda x: x.replace("\xa0", ' '))
df.sample(10)
Out[18]:
Continent Country Expenditure Item 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
518 Africa Republic of South Africa Miscellanous 51.01 56.75 56.25 61.76 46.12 65.88 67.75 66.46 74.00 48.28
17 East Asia Cambodia Entertainment 568.26 573.24 543.35 543.58 599.22 625.88 882.23 200.44 217.44 201.68
498 Middle East United Arab Emirates Miscellanous 64.77 71.05 65.85 79.55 73.14 75.26 88.37 98.24 109.40 109.17
293 Europe East Europe Food and beverage 788.02 823.83 869.72 929.65 956.07 998.67 999.95 1011.25 1020.52 731.19
84 East Asia Vietnam Sight seeing 145.72 160.88 156.39 139.84 144.76 146.58 149.81 137.98 133.49 115.48
347 The Americas U.S.A. Entertainment 595.92 604.11 600.73 615.63 602.02 650.02 623.70 530.17 465.21 201.46
244 Europe Russia Sight seeing 196.34 213.69 217.21 202.28 173.22 173.32 153.54 154.41 155.27 116.52
179 Europe Denmark Medical Care 0.00 0.00 0.00 0.00 0.00 0.00 0.00 67.53 41.46 18.48
76 East Asia Singapore Shopping 1472.14 1560.87 1657.86 1841.34 1944.41 1935.59 1860.68 1862.44 1713.63 1083.06
522 Africa Others Accommodation 1173.94 1243.83 1331.45 1405.76 1484.79 1529.86 1570.27 1481.48 1444.60 946.13

Пропусков в данных нет

In [19]:
df.isna().sum()
Out[19]:
Continent           0
Country             0
Expenditure Item    0
2011                0
2012                0
2013                0
2014                0
2015                0
2016                0
2017                0
2018                0
2019                0
2020                0
dtype: int64

В таблице содержится информация по следующим категориям расходов

In [20]:
df["Expenditure Item"].sort_values().unique()
Out[20]:
array(['Accommodation', 'Entertainment', 'Food and beverage',
       'Local transport', 'Medical Care', 'Miscellanous', 'Shopping',
       'Sight seeing', 'Total ($US)', 'Total (Baht)'], dtype=object)

Посмотрим на boxplot по столбцу "Expenditure (THB)", сгрупированному по столбцу "Expenditure Item":

  • нет отрицательных значений или значений, отличающихся на порядок
  • категории сопоставимы по сумме расходов
  • больше всего тратится на жилье и покупки
In [21]:
df = df.query("Country not in ('Ireland','East Europe','Others')")
expenditure = pd.melt(df, id_vars=['Country', 'Expenditure Item'], value_vars=range(2011, 2021), var_name='Year', value_name='Expenditure (THB)').sort_values(['Country', 'Year'])
fig = px.box(expenditure[~expenditure["Expenditure Item"].isin(['Total ($US)', 'Total (Baht)'])], 
             x="Expenditure Item", y="Expenditure (THB)", color="Expenditure Item", 
             color_discrete_sequence=['purple', 'green', 'yellow', 'plum', 'yellowgreen', 'lemonchiffon', 'orchid', 'olive'])
fig.update_layout(title="Средняя сумма ежедневных расходов по категориям ", title_x=0.5, showlegend=False)
fig.show()

Из столбца "Expenditure Item" следует исключить избыточные категории "Total ($US)" и "Total (Baht)".

In [22]:
expenditure = expenditure.pivot(columns='Expenditure Item', index=['Country', 'Year'], values='Expenditure (THB)').reset_index()
expenditure.drop(['Total ($US)', 'Total (Baht)'], axis=1, inplace=True)
expenditure
Out[22]:
Expenditure Item Country Year Accommodation Entertainment Food and beverage Local transport Medical Care Miscellanous Shopping Sight seeing
0 Argentina 2011 1360.47 551.13 820.26 540.01 0.00 55.88 838.16 124.28
1 Argentina 2012 1395.18 557.54 824.53 563.98 0.00 59.38 854.34 134.70
2 Argentina 2013 1440.00 550.91 868.63 589.59 0.00 51.89 856.13 137.31
3 Argentina 2014 1503.65 545.38 919.44 620.20 0.00 49.59 916.60 158.42
4 Argentina 2015 1512.55 508.94 981.65 651.20 0.00 43.09 890.92 154.51
... ... ... ... ... ... ... ... ... ... ...
445 Vietnam 2016 1315.21 434.01 967.18 447.71 0.00 71.76 1741.16 146.58
446 Vietnam 2017 1248.62 384.14 1042.06 444.97 0.00 77.56 1857.89 149.81
447 Vietnam 2018 1205.15 343.73 1104.68 435.45 53.46 78.27 1729.02 137.98
448 Vietnam 2019 1191.74 346.79 1016.32 415.64 77.35 69.32 1591.58 133.49
449 Vietnam 2020 1118.62 130.64 904.05 335.26 77.78 59.01 991.38 115.48

450 rows × 10 columns

5. ВВП¶

Загрузим данные и отберем те, что соответствуют нужным годам.

In [23]:
df = pd.read_excel(io=r"world_gdp.xls")
df.drop(labels=['Country Code', 2021] + list(range(1960, 2011)), axis=1, inplace=True)
df.sample(10)
Out[23]:
Country Name 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
67 Egypt 2.359897e+11 2.791167e+11 2.884341e+11 3.055954e+11 3.293666e+11 3.324417e+11 2.357337e+11 2.497130e+11 3.030809e+11 3.652527e+11
205 Sao Tome and Principe 2.314893e+08 2.506808e+08 3.005545e+08 3.465283e+08 3.160661e+08 3.454956e+08 3.756141e+08 4.122538e+08 4.274250e+08 4.725510e+08
57 Czechia 2.295627e+11 2.088577e+11 2.116856e+11 2.093588e+11 1.880331e+11 1.962721e+11 2.186289e+11 2.490005e+11 2.525482e+11 2.459746e+11
189 Panama 3.468622e+10 4.042970e+10 4.559990e+10 4.992140e+10 5.409180e+10 5.790770e+10 6.220270e+10 6.492940e+10 6.698440e+10 5.397700e+10
183 OECD members 4.867149e+13 4.861931e+13 4.926724e+13 5.027546e+13 4.740883e+13 4.832536e+13 5.044393e+13 5.336583e+13 5.387859e+13 5.251705e+13
90 Gibraltar NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
75 Europe & Central Asia 2.330552e+13 2.244977e+13 2.345250e+13 2.377971e+13 2.048415e+13 2.041863e+13 2.165394e+13 2.319356e+13 2.290998e+13 2.213998e+13
253 United Kingdom 2.666403e+12 2.706341e+12 2.786315e+12 3.065223e+12 2.934858e+12 2.699660e+12 2.683399e+12 2.878152e+12 2.857058e+12 2.704609e+12
109 IDA only 8.856042e+11 8.912987e+11 9.673684e+11 1.033527e+12 1.039334e+12 1.120362e+12 1.233428e+12 1.229623e+12 1.297064e+12 1.321285e+12
158 Middle East & North Africa 3.354142e+12 3.627526e+12 3.565646e+12 3.592212e+12 3.164191e+12 3.192322e+12 3.313255e+12 3.477249e+12 3.472361e+12 3.115193e+12

Пропусков в данных нет

In [24]:
df.isna().sum()
Out[24]:
Country Name     0
2011             6
2012             8
2013             7
2014             6
2015             8
2016             9
2017             9
2018             9
2019            11
2020            14
dtype: int64
In [25]:
gdp = pd.melt(df, id_vars=['Country Name'], value_vars=range(2011, 2021), var_name='Year', value_name='GDP').rename(columns={'Country Name': 'Country'}).sort_values(['Country', 'Year'])
gdp.Country = gdp.Country.apply(lambda x: x.replace("\xa0", ' '))
gdp.sample(10)
Out[25]:
Country Year GDP
1772 Niger 2017 1.118510e+10
2231 Hong Kong 2019 3.630525e+11
1053 Uruguay 2014 5.723601e+10
1756 Middle East & North Africa (IDA & IBRD countries) 2017 1.448152e+12
2646 United Arab Emirates 2020 3.494730e+11
512 Turkmenistan 2012 3.516421e+10
1446 Isle of Man 2016 6.846692e+09
267 Africa Eastern and Southern 2012 9.725734e+11
279 Australia 2012 1.546892e+12
1532 Rwanda 2016 8.690878e+09

Посмотрим на boxplot по столбцу "GDP":

  • нет отрицательных значений
  • нет выбросов
  • данные находятся в пределах, не противоречащих здравому смыслу
  • большой разброс значений демонстрирует неравномерность в "богатстве" стран, откуда прибывают туристы
In [26]:
fig = px.box(gdp, y="GDP", color_discrete_sequence=['purple'])
fig.update_layout(title="Годовой ВВП", title_x=0.5)
fig.show()

Соединение таблиц¶

In [27]:
final_data = pd.merge(stay_len, visit_freq, on=['Country','Year'])
final_data = pd.merge(final_data, transport_type, on=['Country','Year'])
final_data = pd.merge(final_data, expenditure, on=['Country','Year'])
final_data = pd.merge(final_data, gdp, on=['Country','Year'])
final_data
Out[27]:
Country Year Average length of stay (days) First visit Re-visit Air Land Waterway Accommodation Entertainment Food and beverage Local transport Medical Care Miscellanous Shopping Sight seeing GDP
0 Argentina 2011 11.91 7252 4753 10829 1376 765 1360.47 551.13 820.26 540.01 0.00 55.88 838.16 124.28 5.301633e+11
1 Argentina 2012 12.51 10800 6095 15096 1892 865 1395.18 557.54 824.53 563.98 0.00 59.38 854.34 134.70 5.459824e+11
2 Argentina 2013 13.07 13208 6874 18358 2012 665 1440.00 550.91 868.63 589.59 0.00 51.89 856.13 137.31 5.520251e+11
3 Argentina 2014 13.30 13199 7209 19340 1698 400 1503.65 545.38 919.44 620.20 0.00 49.59 916.60 158.42 5.263197e+11
4 Argentina 2015 14.61 16443 11607 25952 1638 460 1512.55 508.94 981.65 651.20 0.00 43.09 890.92 154.51 5.947493e+11
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
435 Vietnam 2016 5.98 341267 522540 562358 219252 48610 1315.21 434.01 967.18 447.71 0.00 71.76 1741.16 146.58 2.570960e+11
436 Vietnam 2017 5.78 369763 615932 687299 201228 46652 1248.62 384.14 1042.06 444.97 0.00 77.56 1857.89 149.81 2.813536e+11
437 Vietnam 2018 6.54 395049 658674 785034 197572 44654 1205.15 343.73 1104.68 435.45 53.46 78.27 1729.02 137.98 3.101065e+11
438 Vietnam 2019 7.59 361973 718636 821096 186800 39767 1191.74 346.79 1016.32 415.64 77.35 69.32 1591.58 133.49 3.343653e+11
439 Vietnam 2020 11.53 35654 95654 103654 6638 21835 1118.62 130.64 904.05 335.26 77.78 59.01 991.38 115.48 3.466158e+11

440 rows × 17 columns

Получаем полный датасет. Теперь будет удобнее сравнивать различные показатели.

In [28]:
final_data.describe()
Out[28]:
Average length of stay (days) First visit Re-visit Air Land Waterway Accommodation Entertainment Food and beverage Local transport Medical Care Miscellanous Shopping Sight seeing GDP
count 440.000000 4.400000e+02 4.400000e+02 4.400000e+02 4.400000e+02 440.000000 440.000000 440.000000 440.000000 440.000000 440.000000 440.000000 440.000000 440.000000 4.400000e+02
mean 12.107386 2.234573e+05 3.747420e+05 4.831458e+05 9.486607e+04 12827.597727 1439.339068 495.309705 955.103591 492.780250 38.264614 64.414614 1180.075705 152.373795 1.563401e+12
std 4.494754 6.174363e+05 6.176659e+05 1.095184e+06 3.479132e+05 35206.136234 245.995951 167.006642 175.490379 75.711605 136.741247 21.013600 495.795351 45.530000 3.247911e+12
min 4.640000 4.160000e+02 1.862000e+03 1.612000e+03 0.000000e+00 1.000000 638.910000 40.030000 400.330000 178.950000 0.000000 15.020000 250.830000 24.810000 8.750107e+09
25% 7.680000 2.680075e+04 5.318325e+04 7.156525e+04 1.015500e+03 458.000000 1264.762500 394.737500 832.032500 446.972500 0.000000 48.987500 762.825000 121.300000 2.700458e+11
50% 12.805000 6.034150e+04 1.311165e+05 1.595665e+05 5.311000e+03 1824.500000 1416.735000 477.915000 935.545000 488.875000 0.000000 60.630000 1060.370000 152.010000 4.330429e+11
75% 16.405000 2.146445e+05 4.905412e+05 6.108315e+05 3.433400e+04 7956.500000 1592.697500 579.862500 1059.787500 538.540000 24.635000 77.617500 1590.792500 178.175000 1.549296e+12
max 20.280000 5.762730e+06 5.469469e+06 1.065960e+07 2.825754e+06 420769.000000 2173.990000 1222.800000 1477.980000 745.960000 1234.870000 156.050000 2388.260000 347.340000 2.138098e+13
In [29]:
final_data.to_csv("Thailand_Tourism_2011_2020.csv")

Проверка гипотез¶

Гипотеза 1.¶

С увеличением среднего времени пербывания в странe изменяется структура ежедневных трат по категориям.
Иными словами, существует корелляция между средним количеством дней пребывания и долями категорий от средней суммы ежедневных расходов.

Предварительно нужно перевести абсолютное значение расходов по категориям трат в относительное.

In [30]:
total_daily_expenditure = (final_data.Accommodation + final_data.Entertainment + final_data["Food and beverage"] + 
                           final_data["Local transport"] + final_data["Medical Care"] + final_data.Miscellanous +
                           final_data.Shopping + final_data["Sight seeing"])

relative_daily_expenditure = final_data.iloc[:, 8:16].apply(lambda x: x / total_daily_expenditure)

h1_data = pd.merge(final_data["Average length of stay (days)"], relative_daily_expenditure, left_index=True, right_index=True)

h1_data.corr().iloc[:,0]
Out[30]:
Average length of stay (days)    1.000000
Accommodation                    0.611256
Entertainment                    0.130424
Food and beverage                0.650510
Local transport                  0.688258
Medical Care                    -0.019858
Miscellanous                    -0.347299
Shopping                        -0.789257
Sight seeing                     0.264714
Name: Average length of stay (days), dtype: float64

Получили коэффициенты корелляции между средним количеством дней пребывания и долями категорий от средней суммы ежедневных расходов. Видим, что существует положительная линейная зависимость между кол-вом дней пребывания и долей ежедневных трат на категории "Жилье", "Еда и напитки", "Внутренный транспорт". С другой стороны, есть сильная отрицательная линейная зависимость между кол-вом дней пребывания и долей ежедневных трат на категорию "Шоппинг".
Таким образом, можно предположить, что в Таиланде туристы с коротким сроком пребывания больше тратят на покупки, а с увеличением кол-ва дней отпуска увеличивается доля трат на бытовые нужды: жилье, еду, транспорт (при долгом нахождении в стране образ жизни становится более рутинным и схожим с местными жителями).

Гипотеза 2.¶

Средняя сумма ежедневных трат на жилье подчиняется нормальному распределению.

Взглянем на гистограмму.

In [31]:
fig = px.histogram(final_data["Accommodation"], 
                   histnorm='probability', 
                   color_discrete_sequence=['purple'])
fig.update_layout(title="Плотность распределения ежедневных трат на жилье", 
                  title_x=0.5, showlegend=False, xaxis=None, yaxis=None)
fig.show()

Визуально по форме графика можно предположить, что средняя ежедневная сумма расходов на жилье имеет нормальное распределение. Но еще стоит проверить гипотезу о нормальности распределения, используя критерий $\chi^2$ Пирсона с уровнем значимости $\alpha = 0.05$.

In [32]:
_, p_value = scipy.stats.normaltest(final_data.Accommodation)
p_value
Out[32]:
0.01982729899262304

$p$-значение меньше уровня значимости $\alpha = 0.05$, значит гипотеза о нормальности распределения отвергается. То есть средняя сумма ежедневных трат не подчиняется нормальному распределению.

Гипотеза 3.¶

Значения годового ВВП стран, откуда прибывают туристы, и количество прибытий из этих стран в год распределены одинаково.
Мировой ВВП среди стран распространен неравномерно. Есть несколько стран с высоким ВВП, составляющим большую часть мирвого. Остальной мировой ВВП распределен между сотнями других стран с низким ВВП.
Есть предположение, что кол-во прибытий туристов подчиняется схожему закону: есть несколько стран, отвечающих за большую часть турпотока, и множество стран, откуда прибывает относительно немного туристов.
Проверим это, сравнив распределения выборок данных величин.

Предварительно лучше нормализовать значения выборок, чтобы они лежали в одинаковых границах и были сопоставимы.

In [33]:
gdp_norm = (final_data["GDP"] - min(final_data["GDP"])) / (max(final_data["GDP"]) - min(final_data["GDP"]))

visits_norm = final_data["First visit"] + final_data["Re-visit"]
visits_norm = (visits_norm - min(visits_norm)) / (max(visits_norm) - min(visits_norm))

fig1 = px.histogram(gdp_norm, 
                   histnorm='probability', nbins=21, 
                   color_discrete_sequence=['purple'])
fig1.update_layout(title="Плотность распределения ежегодного ВВП стран-источников турпотока", 
                  title_x=0.5, showlegend=False, xaxis=None, yaxis=None)
fig1.show()

fig2 = px.histogram(visits_norm, 
                   histnorm='probability', nbins=21, 
                   color_discrete_sequence=['purple'])
fig2.update_layout(title="Плотность распределения размера ежегодного турпотока из каждой страны", 
                  title_x=0.5, showlegend=False, xaxis=None, yaxis=None)
fig2.show()

Исходя из визуального анализа гистограмм можно увидеть, что распределения двух выборок очень похожи. Используем критерий Колмогорова-Смирнова для статистической проверки нашей гипотезы. Уровень значимости $\alpha = 0.05$.

In [34]:
_, p_value = scipy.stats.kstest(gdp_norm, visits_norm)
p_value
Out[34]:
9.776477159692956e-05

$p$-значение меньше уровня значимости $\alpha = 0.05$, значит гипотеза об однородности распределений двух выборок отвергается. То есть значения годового ВВП стран-источников турпотока и количество прибытий из этих стран в год имеют различные распределения.

Визуализация¶

В качестве продукта этого разведочного анализа было решено создать интерактивный user-friendly дашборд из нескольких графиков и карт, визуализирующих исследуемые данные. Основной инструмент - Tableau.
Дашборд доступен по ссылке.